package com.masnegocio.pac.util.xml;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.apache.log4j.Logger;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.masnegocio.pac.comun.Base64;
import com.masnegocio.pac.comun.PacConstantes;
import com.masnegocio.pac.comun.PacException;
import com.masnegocio.pac.dto.DatosDto;
import com.masnegocio.pac.util.archivo.ArchivoHandler;

public class ManagerXml {
	private static final Logger logger = Logger.getLogger(ManagerXml.class);
	private static Map<String,Validator> mapaXSD;
	private static Map<String,Transformer> mapaXSLT;
	
	/**
	 * 
	 * @param version indica la version de XSD contra la que hay que validar el XML.
	 * @param byteXml es el XML el cual sera validado.
	 * @return boolean true si el xml es valido contra la version y false en caso contrario.
	 * @throws PacException
	 */
	public static boolean validateXml(String version, byte[] byteXml) throws PacException{
		Validator validator = null;
		if(logger.isDebugEnabled()){
			logger.debug("SE VA A VALIDAR UN Xml PARA LA VERSION: "+ version);
		}
		if(mapaXSD==null){
			mapaXSD=new HashMap<String,Validator>();
		}
		if(mapaXSD.containsKey(version)){
			validator = mapaXSD.get(version);
		}else{
			SchemaFactory factory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI);
			try {
				Schema schema = factory.newSchema(ArchivoHandler.obtenerXsdVersion(version));
				validator = schema.newValidator();
			} catch (SAXException saxE) {
				logger.error("SE PRODUJO UN ERROR AL MOMENTO DE CREAR EL SCHEMA LA EXCEPTION ES: "+saxE);
				throw new PacException(saxE, PacConstantes.CODIGO_ERROR_CREAR_SCHEMA);
			}	
		}
		ByteArrayInputStream byteArray = new ByteArrayInputStream(byteXml);
		SAXSource source = new SAXSource(new InputSource(byteArray));
		try {
			validator.validate(source);
		} catch (SAXException saxE) {
			logger.error("SE PRODUJO UN ERROR AL MOMENTO DE VALIDAR EL XML LA EXCEPTION ES: "+saxE);
			throw new PacException(saxE, PacConstantes.CODIGO_ERROR_VALIDA_XML);
		} catch (IOException ioE) {
			logger.error("SE PRODUJO UN ERROR AL MOMENTO DE LEER EL XML LA EXCEPTION ES: "+ioE);
			throw new PacException(ioE, PacConstantes.CODIGO_ERROR_LECTURA_ARCHIVO_XML);
		}finally{
			try {
				byteArray.close();
			} catch (IOException ioE) {
				logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+ioE);
				throw new PacException(ioE, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
			}
		}
		return true;
	}
	
	/**
	 * 
	 * @param version indica la version deL XSlt con la que hay transformar el XML.
	 * @param byteXml es el XML el cual sera transformado.
	 * @return es el String con la cadena original creada.
	 * @throws PacException
	 */
	public static String generarCadenaOriginal(String version, byte[] byteXml)throws PacException{
		ByteArrayInputStream byteArray = new ByteArrayInputStream(byteXml);
		SAXSource sourceXML = new SAXSource(new InputSource(byteArray));
		Transformer trans = null;
		ByteArrayOutputStream byteArrayOutputStream = null;
		if(mapaXSLT==null){
			mapaXSLT = new HashMap<>();
		}
		if(mapaXSLT.containsKey(version)){
			trans = mapaXSLT.get(version);
		}else{
			Source xsltSource = null;
			try {
				xsltSource = new StreamSource(ArchivoHandler.obtenerXsltVersion(version));
				if(logger.isDebugEnabled()){
					logger.debug("SE CREA EL SOURCE DEL XSLT");
				}
			}catch(Exception e){
				try {
					byteArray.close();
				} catch (IOException ioe) {
					logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+ioe);
					throw new PacException(ioe, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
				}
			}
			TransformerFactory transFact = TransformerFactory.newInstance();
			try {
				trans = transFact.newTransformer(xsltSource);
				mapaXSLT.put(version, trans);
				if(logger.isDebugEnabled()){
					logger.debug("SE CREO EL TRANSFORMER PARA APLICAR EL XSLT");
				}
			} catch (TransformerConfigurationException e) {
				try {
					byteArray.close();
				} catch (IOException ioe) {
					logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+ioe);
					throw new PacException(ioe, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
				}
				logger.error("SE PRODUJO UN ERROR AL MOMENTO DE CREAR EL OBJETO PARA APLICAR EL XSLT: "+e);
				throw new PacException(e, PacConstantes.CODIGO_ERROR_CREAR_XSLT);
			}
		}
		try {
			byteArrayOutputStream = new ByteArrayOutputStream();
			trans.transform(sourceXML, new StreamResult(byteArrayOutputStream));
			if(logger.isDebugEnabled()){
				logger.debug("SE APLICO EL XSLT AL XML");
			}
		} catch (TransformerException e) {
			try {
				byteArray.close();
			} catch (IOException ioe) {
				logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+ioe);
				throw new PacException(ioe, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
			}
			logger.error("SE PRODUJO UN ERROR AL MOMENTO DE APLICAR EL XSLT: "+e);
			throw new PacException(e, PacConstantes.CODIGO_ERROR_APLICAR_XSLT);
		}
		String cadenaOriginal = new String(byteArrayOutputStream.toByteArray());
		if(logger.isDebugEnabled()){
			logger.debug("SE GENERO LA CADENA ORIGINAL: "+cadenaOriginal);
		}
		try {
			byteArray.close();
		} catch (IOException e) {
			logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+e);
			throw new PacException(e, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
		}
		return cadenaOriginal;
	}
	
	
	public static DatosDto obtenerDatosXSLT(byte[] byteXml)throws PacException{
		ByteArrayInputStream byteArray = new ByteArrayInputStream(byteXml);
		SAXSource sourceXML = new SAXSource(new InputSource(byteArray));
		Transformer trans = null;
		ByteArrayOutputStream byteArrayOutputStream = null;
		if(mapaXSLT==null){
			mapaXSLT = new HashMap<>();
		}
		if(mapaXSLT.containsKey(PacConstantes.KEY_XSLT_DATO)){
			trans = mapaXSLT.get(PacConstantes.KEY_XSLT_DATO);
		}else{
			Source xsltSource = null;
			try {
				xsltSource = new StreamSource(ArchivoHandler.obtenerXsltDatosVersion());
				if(logger.isDebugEnabled()){
					logger.debug("SE CREA EL SOURCE DEL XSLT");
				}
			}catch(Exception e){
				try {
					byteArray.close();
				} catch (IOException ioe) {
					logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+ioe);
					throw new PacException(ioe, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
				}
			}
			TransformerFactory transFact = TransformerFactory.newInstance();
			try {
				trans = transFact.newTransformer(xsltSource);
				mapaXSLT.put(PacConstantes.KEY_XSLT_DATO, trans);
				if(logger.isDebugEnabled()){
					logger.debug("SE CREO EL TRANSFORMER PARA APLICAR EL XSLT");
				}
			} catch (TransformerConfigurationException e) {
				try {
					byteArray.close();
				} catch (IOException ioe) {
					logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+ioe);
					throw new PacException(ioe, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
				}
				logger.error("SE PRODUJO UN ERROR AL MOMENTO DE CREAR EL OBJETO PARA APLICAR EL XSLT: "+e);
				throw new PacException(e, PacConstantes.CODIGO_ERROR_CREAR_XSLT);
			}
		}
		try {
			byteArrayOutputStream = new ByteArrayOutputStream();
			trans.transform(sourceXML, new StreamResult(byteArrayOutputStream));
			if(logger.isDebugEnabled()){
				logger.debug("SE APLICO EL XSLT AL XML");
			}
		} catch (TransformerException e) {
			try {
				byteArray.close();
			} catch (IOException ioe) {
				logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+ioe);
				throw new PacException(ioe, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
			}
			logger.error("SE PRODUJO UN ERROR AL MOMENTO DE APLICAR EL XSLT: "+e);
			throw new PacException(e, PacConstantes.CODIGO_ERROR_APLICAR_XSLT);
		}
		String stringDatos = new String(byteArrayOutputStream.toByteArray());
		DatosDto datosDto = null;
		try{
			datosDto = new DatosDto(stringDatos.split("\\|"));
		}catch (Exception e) {
			logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CREAR EL DTO DE DATOS LA EXCEPTION ES: "+e);
			throw new PacException(e, PacConstantes.CODIGO_ERROR_CREAR_DTO_RESPUESTA);
		}
		try {
			byteArray.close();
		} catch (IOException e) {
			logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+e);
			throw new PacException(e, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
		}
		return datosDto;
	}
	
	/**
	 * 
	 * @param byteXml es el XML del cual se obtenendran los datos sensibles necesarios.
	 * @return DatosDto regresa un dto con todos los datos sensibles.
	 * @throws PacException en caso de una exception se captura y se dispara una exception concida.
	 */
	public static DatosDto obtenerDatos(byte[] byteXml)throws PacException{
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(byteXml)));
		DatosDto datosDto = new DatosDto();
			String line = null;
			try {
				line = bufferedReader.readLine();
				if(logger.isDebugEnabled()){
					logger.debug("SE INICIA A RECORRER EL XML EN BUSQUEDA DE LOS DATOS SELLO, VERSION, CERTIFICADO, FECHA Y RFC");
				}
				recorreXml:
				while (line !=null){				
					boolean validacionUno = false, validacionDos= false;
					if(!validacionUno&&line.contains(PacConstantes.DATO_VERSION)&&line.contains(PacConstantes.DATO_SELLO)&&
							line.contains(PacConstantes.DATO_CERTIFICADO)&&line.contains(PacConstantes.DATO_FECHA)&&line.contains(PacConstantes.DATO_FOLIO)){
						String [] arreglo = line.split("\"");
						for (int i = 0; i<arreglo.length; i++){
							if(arreglo[i].contains(PacConstantes.DATO_VERSION)){
								int j  = i+1;
								datosDto.setVersion(arreglo[j]);
								if(logger.isDebugEnabled()){
									logger.debug("SE OBTUVO EL DATO VERSION");
								}
							}else if(arreglo[i].contains(PacConstantes.DATO_SELLO)){
								int j  = i+1;
								datosDto.setSello(arreglo[j]);
								if(logger.isDebugEnabled()){
									logger.debug("SE OBTUVO EL DATO SELLO");
								}
							}else if(arreglo[i].contains(PacConstantes.DATO_CERTIFICADO)){
								int j  = i+1;
								datosDto.setCertificado(Base64.decode(arreglo[j]));
								if(logger.isDebugEnabled()){
									logger.debug("SE OBTUVO EL DATO CERTIFICADO");
								}
							}else if(arreglo[i].contains(PacConstantes.DATO_FECHA)){
								int j  = i+1;
								datosDto.setFechaEmision(arreglo[j]);
								if(logger.isDebugEnabled()){
									logger.debug("SE OBTUVO EL DATO FECHA");
								}
							}else if(arreglo[i].contains(PacConstantes.DATO_FOLIO)){
								int j  = i+1;
								datosDto.setFolio(arreglo[j]);
								if(logger.isDebugEnabled()){
									logger.debug("SE OBTUVO EL DATO FOLIO");
								}
							}
						}
						validacionUno=true;
					}else if(!validacionDos&&line.contains(PacConstantes.DATO_EMISOR)&&line.contains(PacConstantes.DATO_RFC)){
						String [] arreglo = line.split("\"");
						buscaRfc:
						for (int i = 0; i<arreglo.length; i++){
							if(arreglo[i].contains(PacConstantes.DATO_RFC)){
								int j  = i+1;
								datosDto.setRfcEmisor(arreglo[j]);
								if(logger.isDebugEnabled()){
									logger.debug("SE OBTUVO EL DATO RFC");
								}
								break buscaRfc;
							}
						}
						validacionDos=true;
					}
					if(validacionUno&&validacionDos){
						break recorreXml;
					}
					line = bufferedReader.readLine();
				}
			} catch (IOException e) {
				logger.error("SE PRODUJO UN ERROR AL MOMENTO DE ESTAR LEYENDO EL XML LA EXCPETION ES: "+e);
				throw new PacException(e, PacConstantes.CODIGO_ERROR_LECTURA_ARCHIVO_XML);
			}finally{
				try {
					bufferedReader.close();
				} catch (IOException e) {
					logger.error("SE PRODUJO UN ERROR AL MOMENTO DE INTENTAR CERRAR EL XML LA EXCEPTION ES: "+e);
					throw new PacException(e, PacConstantes.CODIGO_ERROR_CERRADO_ARCHIVO_XML);
				}
			}
		return datosDto;
	}
}